package cn.k7g.alloy.ioc.conversion;

import cn.k7g.alloy.annotation.AlloyContent;
import cn.k7g.alloy.exception.AlloyMessageException;
import cn.k7g.alloy.model.AlloyContentHold;
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * 处理参数中的 alloy content 解码部分
 *
 * 接管 mvcConversionService， 如果重写了 ConfigurableWebBindingInitializer
 * 可能会导致 AlloyConversionService 不生效，注意配置。 参考如下代码位置
 * @see org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#getConfigurableWebBindingInitializer(org.springframework.format.support.FormattingConversionService, org.springframework.validation.Validator)
 *
 *
 * 在 ConfigurableWebBindingInitializer.setConversionService 中，建议将 ConversionService 交给 AlloyConversionService 处理
 *
 * AlloyConversionService 的初始化代码 参考
 * @see cn.k7g.alloy.ioc.processor.EnhanceAlloyConversionServiceProcess
 *
 *
 * @author victor-wu
 * @date 2021/9/27 下午1:10
 */
public class AlloyConversionService extends WebConversionService {
    /**
     * Create a new WebConversionService that configures formatters with the provided
     * date, time, and date-time formats, or registers the default if no custom format is
     * provided.
     *
     * @param dateTimeFormatters the formatters to use for date, time, and date-time
     *                           formatting
     * @since 2.3.0
     */
    public AlloyConversionService(DateTimeFormatters dateTimeFormatters) {
        super(dateTimeFormatters);
    }


    @Nullable
    public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (targetType.hasAnnotation(AlloyContent.class)) {
            // 创建临时 TypeDescriptor 用于匹配到 AlloyContentConverter
            targetType = new TypeDescriptor(ResolvableType.forClass(AlloyContentHold.class), AlloyContentHold.class, targetType.getAnnotations());
        }
        return super.getConverter(sourceType, targetType);
    }

    @Override
    @Nullable
    public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
        Assert.notNull(targetType, "Target type to convert to cannot be null");
        if (sourceType == null) {
            Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
            return handleResult(null, targetType, convertNullSource(null, targetType));
        }
        if (source != null && !sourceType.getObjectType().isInstance(source)) {
            throw new IllegalArgumentException("Source to convert from must be an instance of [" +
                    sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
        }
        GenericConverter converter = getConverter(sourceType, targetType);
        if (converter != null) {
            // 重载 ConversionUtils.invokeConverter 实现 AlloyMessageException 异常
            Object result = ConversionUtils_invokeConverter(converter, source, sourceType, targetType);
            return handleResult(sourceType, targetType, result);

        }
        return handleConverterNotFound(source, sourceType, targetType);
    }

    /*
      -----------------   此处开始下面都属于是 spring 中的源码，解决异常重载。不同版本可能会有差异，如果发现了问题请立即反馈
     */

    Object ConversionUtils_invokeConverter(GenericConverter converter, @Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        try {
            Object result = converter.convert(source, sourceType, targetType);
            return handleResult(sourceType, targetType, result);
        } catch (AlloyMessageException | ConversionFailedException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new ConversionFailedException(sourceType, targetType, source, ex);
        }
    }

    @Nullable
    private Object handleResult(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType, @Nullable Object result) {
        if (result == null) {
            assertNotPrimitiveTargetType(sourceType, targetType);
        }
        return result;
    }
    private void assertNotPrimitiveTargetType(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (targetType.isPrimitive()) {
            throw new ConversionFailedException(sourceType, targetType, null,
                    new IllegalArgumentException("A null value cannot be assigned to a primitive type"));
        }
    }
    @Nullable
    private Object handleConverterNotFound(
            @Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {

        if (source == null) {
            assertNotPrimitiveTargetType(sourceType, targetType);
            return null;
        }
        if ((sourceType == null || sourceType.isAssignableTo(targetType)) &&
                targetType.getObjectType().isInstance(source)) {
            return source;
        }
        throw new ConverterNotFoundException(sourceType, targetType);
    }

}
